﻿namespace BathroomLocator.Services
{
	using System;
	using System.Globalization;
	using System.IO;
	using System.Linq;
	using System.Net;
	using System.Web;
	using System.Web.Security;
	using System.Xml;
	using BathroomLocator.Services.Infrastructure;
	using BathroomLocator.Services.MembershipWrappers;
	using BathroomLocator.Services.Serializers;
	using Microsoft.WindowsAzure;

	public class AzureTablesHandler : IHttpHandler
	{
		public AzureTablesHandler() :
			this(new RoleProviderWrapper(), new StorageRequestValidator(), new FormatSerializerFactory(), CloudStorageAccount.FromConfigurationSetting("DataConnectionString"))
		{
		}

		public AzureTablesHandler(RoleProvider roleProvider, IStorageRequestValidator requestValidator, IFormatSerializerFactory formatSerializerFactory, CloudStorageAccount cloudStorageAccount)
		{
			this.RoleProvider = roleProvider;
			this.RequestValidator = requestValidator;
			this.FormatSerializerFactory = formatSerializerFactory;
			this.CloudStorageAccount = cloudStorageAccount;
		}

		public bool IsReusable
		{
			get { return true; }
		}

		public RoleProvider RoleProvider { get; private set; }

		public IStorageRequestValidator RequestValidator { get; private set; }

		public IFormatSerializerFactory FormatSerializerFactory { get; private set; }

		public CloudStorageAccount CloudStorageAccount { get; private set; }

		public void ProcessRequest(HttpContext context)
		{
			if (this.RequestValidator.ValidateRequest(context))
			{
				var table = StorageRequestAnalyzer.GetRequestedTable(context.Request);
				var tableToCreate = string.Empty;

				if (StorageRequestAnalyzer.IsCreatingTable(context.Request, table))
				{
					tableToCreate = StorageRequestAnalyzer.GetTableToCreate(context.Request);
				}

				var azureResponse = this.CopyRequestToAzureStorage(context);

				var serializer = this.FormatSerializerFactory.GetSerializer(context.Request.Headers, context.Request.QueryString);
				var originalBody = ExtractBodyFromResponse(azureResponse);
				var serializedContentType = string.Empty;
				var body = string.Empty;
				var isBodyEmpty = string.IsNullOrWhiteSpace(originalBody);

				if (!isBodyEmpty)
				{
					var xmlDoc = new XmlDocument();
					xmlDoc.LoadXml(originalBody);

					body = serializer.SerializeReply(xmlDoc, out serializedContentType);
				}

				CopyAzureHeadersToResponse(serializedContentType, azureResponse, context.Response);
				CopyAzureBodyToResponse(body, context.Response, context.Request.Url, context.Request.FilePath);

				if (context.Response.StatusCode == 201)
				{
					if (!string.IsNullOrEmpty(tableToCreate))
					{
						// add permissions to the current user to its new table
						var userName = this.RequestValidator.GetUserName(context);

						this.AddPermissionToNewTable(tableToCreate, userName);
					}
				}

				context.Response.End();
			}
		}

		private static void CopyRequestHeaders(HttpRequest originalRequest, WebRequest finalRequest)
		{
			finalRequest.ContentType = originalRequest.ContentType;
			finalRequest.ContentLength = originalRequest.ContentLength;
			finalRequest.Headers.Add("DataServiceVersion", "1.0;NetFx");
			finalRequest.Headers.Add("MaxDataServiceVersion", "2.0;NetFx");

			var finalHttpWebRequest = finalRequest as HttpWebRequest;
			if (finalHttpWebRequest != null)
			{
				finalHttpWebRequest.Accept = HttpConstants.MimeApplicationAtomXml;
				finalHttpWebRequest.Connection = originalRequest["Connection"];
				finalHttpWebRequest.Referer = originalRequest.Headers["Referer"];
				finalHttpWebRequest.UserAgent = originalRequest.Headers["User-Agent"];
			}

			string[] excludeHeaders =
                {
                    "Connection",
                    "Accept",
                    "User-Agent",
                    "Host",
                    "DataServiceVersion",
                    "MaxDataServiceVersion",
                    "Authorization",
                    "AuthToken",
                    "x-ms-date",
                    "x-ms-version",
                    "Content-Length",
                    "Content-Type",
                    "Referer"
                };

			foreach (var header in originalRequest.Headers.AllKeys)
			{
				if (!excludeHeaders.Contains(header))
				{
					finalRequest.Headers.Add(header, originalRequest.Headers[header]);
				}
			}
		}

		private static string ExtractBodyFromResponse(WebResponse azureResponse)
		{
			using (var stream = azureResponse.GetResponseStream())
			{
				return new StreamReader(stream).ReadToEnd();
			}
		}

		private static void CopyAzureHeadersToResponse(string contentType, WebResponse originalResponse, HttpResponse finalResponse)
		{
			finalResponse.ContentType = contentType;

			var httpWebResponse = originalResponse as HttpWebResponse;
			if (httpWebResponse != null)
			{
				finalResponse.StatusCode = (int)httpWebResponse.StatusCode;
				finalResponse.StatusDescription = httpWebResponse.StatusDescription;
			}

			string[] excludeHeaders = { "Content-Type", "Transfer-Encoding" };
			foreach (var header in originalResponse.Headers.AllKeys)
			{
				if (!excludeHeaders.Contains(header))
				{
					finalResponse.Headers.Add(header, originalResponse.Headers[header]);
				}
			}
		}

		private static void CopyAzureBodyToResponse(string responseBody, HttpResponse response, Uri requestUrl, string requestPath)
		{
			var storageAccount = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
			var newResponseBody = responseBody.Replace(
							storageAccount.TableEndpoint.GetComponents(UriComponents.SchemeAndServer, UriFormat.SafeUnescaped),
							string.Format(CultureInfo.InvariantCulture, "{0}{1}", requestUrl.GetComponents(UriComponents.SchemeAndServer & ~UriComponents.Port, UriFormat.SafeUnescaped), requestPath));

			response.Write(newResponseBody);
		}

		private static WebRequest CreateWebRequest(Uri newRequestUri)
		{
			return WebRequest.Create(newRequestUri);
		}

		private WebResponse CopyRequestToAzureStorage(HttpContext context)
		{
			var relativeUrl = (context.Request.QueryString.Count > 0)
								? string.Format(CultureInfo.InvariantCulture, "{0}?{1}", context.Request.PathInfo, context.Request.QueryString.ToString())
								: context.Request.PathInfo;

			var devaccount = CloudStorageAccount.DevelopmentStorageAccount.Credentials.AccountName;

			if (CloudStorageAccount.Credentials.AccountName.Equals(devaccount, StringComparison.OrdinalIgnoreCase)
				&& relativeUrl.StartsWith(string.Format(CultureInfo.InvariantCulture, "/{0}", devaccount), StringComparison.OrdinalIgnoreCase))
			{
				relativeUrl = relativeUrl.Remove(0, devaccount.Length + 1);
			}

			var newRequestUri = new Uri(string.Format(CultureInfo.InvariantCulture, "{0}{1}", this.CloudStorageAccount.TableEndpoint, relativeUrl), UriKind.Absolute);
			return this.GetAzureTablesResponse(newRequestUri, context.Request);
		}

		private WebResponse GetAzureTablesResponse(Uri newRequestUri, HttpRequest originalRequest)
		{
			var webRequest = CreateWebRequest(newRequestUri);
			webRequest.Method = originalRequest.HttpMethod;

			CopyRequestHeaders(originalRequest, webRequest);

			var httpWebRequest = webRequest as HttpWebRequest;
			if (httpWebRequest != null)
			{
				this.CloudStorageAccount.Credentials.SignRequestLite(httpWebRequest);
			}

			if (!originalRequest.HttpMethod.Equals("GET", StringComparison.OrdinalIgnoreCase) &&
				!originalRequest.HttpMethod.Equals("DELETE", StringComparison.OrdinalIgnoreCase) &&
				originalRequest.ContentLength > 0)
			{
				var requestBody = string.Empty;
				var inputStream = originalRequest.InputStream;
				using (var reader = new StreamReader(inputStream))
				{
					inputStream = null;
					requestBody = reader.ReadToEnd().Replace(
									string.Format(
										CultureInfo.InvariantCulture,
										"{0}{1}",
										originalRequest.Url.GetComponents(UriComponents.SchemeAndServer & ~UriComponents.Port, UriFormat.SafeUnescaped),
										originalRequest.FilePath),
									newRequestUri.GetComponents(UriComponents.SchemeAndServer, UriFormat.SafeUnescaped));
				}

				webRequest.ContentLength = requestBody.Length;
				var requestStream = webRequest.GetRequestStream();
				using (var writer = new StreamWriter(requestStream))
				{
					requestStream = null;
					writer.Write(requestBody);
				}
			}

			try
			{
				return webRequest.GetResponse();
			}
			catch (WebException webException)
			{
				return webException.Response;
			}
		}

		private void AddPermissionToNewTable(string tableName, string userName)
		{
			if (!string.IsNullOrEmpty(tableName))
			{
				var roleName = string.Format(CultureInfo.InvariantCulture, "{0}{1}", tableName, RoleConstants.TableRoleSuffix);

				if (!this.RoleProvider.RoleExists(roleName))
				{
					this.RoleProvider.CreateRole(roleName);
				}

				if (!this.RoleProvider.IsUserInRole(userName, roleName))
				{
					string[] userNames = { userName };
					string[] roleNames = { roleName };

					this.RoleProvider.AddUsersToRoles(userNames, roleNames);
				}
			}
		}
	}

}
